/**************************************
 * File: GCPtr.h
 * Author: Osborne McGraw-Hill
 * Modified by: Anders "Saasen" Stensaas
 *				Stig Magnus Halvorsen
 *				Peder Thorup
 * 
 * Description: Extracted from Osborne
 *				McGraw-Hill's gc.h. A
 *				single threaded Garbage Collector
 *
 * Source and documentation:
 *	http://www.devarticles.com/c/a/Cplusplus/A-Simple-Garbage-Collector-for-C-plus-plus/3/
 *
 ************************************/

// inclusion guard as we include the cpp file in it's own h file.
#ifndef GCPTR_CPP
#define GCPTR_CPP

#include "GCPtr.h"

using namespace std;

template <class T, int size> GCPtr<T, size>::GCPtr(T *t)
{
	// Register shutdown() as an exit function. This will only be
	// done once as the "first" bool is defined as a static in the
	// single GCInfo object.
	if(GCInfo::first)
		atexit(shutdown);
	GCInfo::first = false;

	// Create an iterator of the pointer object and populate it
	std::list<GCInfo>::iterator p;
	p = findPtrInfo(t);

	// If t is already in gclist, then increment its reference count.
	// Otherwise, add it to the list.
	if(p != GCInfo::gclist.end())
		p->refcount++; // increment ref count
	else {
		// Create and store this entry.
		GCInfo gcObj(t, size);
		GCInfo::gclist.push_front(gcObj);
	}

	// Check if the object is an array
	addr = t;
	arraySize = size;
	if(size > 0) isArray = true;
	else isArray = false;
	
}

// Copy constructor.
template <class T, int size> GCPtr<T, size>::GCPtr(const GCPtr &ob)
{
	// Create an iterator and gain the object's data
	list<GCInfo>::iterator p;
	p = findPtrInfo(ob.addr);

	p->refcount++; // increment ref count
	addr = ob.addr;

	// Check if the object is an array
	arraySize = ob.arraySize;
	if(arraySize > 0) isArray = true;
	else isArray = false;
	
}

// Deconstructor
template <class T, int size> GCPtr<T, size>::~GCPtr()
{
	// Create an iterator to the list, and gather data
	std::list<GCInfo>::iterator p;
	p = findPtrInfo(addr);
	if(p->refcount) p->refcount--; // decrement ref count
	
	
	// Collect garbage when a pointer goes out of scope.   
	collect();

	// For real use, you might want to collect
	// unused memory less frequently, such as after
	// gclist has reached a certain size, after a
	// certain number of GCPtrs have gone out of scope,
	// or when memory is low.
}

// Collect function that frees the memory
template <class T, int size> bool GCPtr<T, size>::collect()
{
	bool memfreed = false; // boolean to control if anything was freed

	showlist();
	
	std::list<GCInfo>::iterator p; // iterator to host the elements of the gclist

	// Loop through all the elements
	for (p = GCInfo::gclist.begin(); p != GCInfo::gclist.end();)
	{
		// Check if it's already deleted/cleared
		if (p->refcount > 0)
		{
			++p; // jump to the next element
		}
		else
		{
			memfreed = true; // something is about to be freed

			// Check if it's an array or single element
			if (p->isArray)
			{
				delete[] p->memPtr; // log and delete the array
			}
			else
			{
				delete p->memPtr; // log and delete the single element
			}

			// Remove the freed element from the list and update our iterator
			// with the next element in the list.
			p = GCInfo::gclist.erase(p);
		}
	}

	// List the memory elements in the log (after clean)
	showlist();

	// Return true/false if something was freed
	return memfreed;
}

// Copy override
template <class T, int size> T * GCPtr<T, size>::operator=(T *t)
{
	// Create an iterator and gain the object's data
	list<GCInfo>::iterator p;
	p = findPtrInfo(addr);

	// First, decrement the reference count
	// for the memory currently being pointed to.
	p->refcount--;

	// Next, if the new address is already
	// existent in the system, increment its
	// count. Otherwise, create a new entry
	// for gclist.
	p = findPtrInfo(t);
	if(p != GCInfo::gclist.end())
	{
		p->refcount++;
	}
	else
	{
		// Create and store this entry.
		GCInfo gcObj(t, size);
		GCInfo::gclist.push_front(gcObj);
	}

	addr = t; // store the address.
	return t;
}

// Copy override
template <class T, int size> GCPtr<T, size>& GCPtr<T, size>::operator=(GCPtr &rv)
{
	// Create an iterator and gain the object's data
	list<GCInfo>::iterator p;
	p = findPtrInfo(addr);

	// First, decrement the reference count
	// for the memory currently being pointed to.
	p->refcount--;

	// Next, increment the reference count of
	// the new address.
	p = findPtrInfo(rv.addr);
	p->refcount++; // increment ref count

	addr = rv.addr;// store the address.
	return rv;
}

// Print all the elements of the memory in the list to the log
template <class T, int size> void GCPtr<T, size>::showlist()
{
	// Create an iterator
	std::list<GCInfo>::iterator p;
	bool runned = false; // bool to prevent double line break

	// Set the log level to "INFO"

	if(GCInfo::gclist.begin() == GCInfo::gclist.end()) {
		return;
	}
	for(p = GCInfo::gclist.begin(); p != GCInfo::gclist.end(); p++) {
		runned = true;
	}
	
	// To prevent double line break
}

// Returns the iterator to a specific pointer
template <class T, int size> typename std::list<GCInfo>::iterator GCPtr<T, size>::findPtrInfo( T *ptr )
{
	// create the iterator
	std::list<GCInfo>::iterator p;

	// Find ptr in gclist.
	for(p = GCInfo::gclist.begin(); p != GCInfo::gclist.end(); p++)
	{
		if(p->memPtr == ptr)
		{
			return p;
		}
	}

	return p;
}

// Shutdown function, runs once on termination of application
template <class T, int size> void GCPtr<T, size>::shutdown()
{
	if(gclistSize() == 0) return; // list is empty, return

	std::list<GCInfo>::iterator p; // iterator
	for(p = GCInfo::gclist.begin(); p != GCInfo::gclist.end(); p++) {
		// Set all reference counts to zero
		p->refcount = 0;
	}
	

	
	collect(); // clean
	
}

// POINTER AND ARRAY OVERRIDES

template <class T, int size> T& GCPtr<T, size>::operator*()
{
	return *addr;
}

template <class T, int size> T* GCPtr<T, size>::operator->()
{
	return addr;
}

template <class T, int size> T& GCPtr<T, size>::operator[](int i)
{
	return addr[i];
}

template <class T, int size> GCPtr<T, size>::operator T *()
{ 
	return addr;
}

// Return an Iter to the start of the allocated memory.  
template <class T, int size> Iter<T> GCPtr<T, size>::begin()
{
	int size;
	if(isArray)
		size = arraySize;
	else
		size = 1;
	return Iter<T>(addr, addr, addr + size);
}

// Return an Iter to one past the end of an allocated array.
template <class T, int size> Iter<T> GCPtr<T, size>::end()
{
	int size;
	if(isArray)
		size = arraySize;
	else
		size = 1;
	return Iter<T>(addr + size, addr, addr + size);
}

// Return the size of gclist for this type of GCPtr.
template <class T, int size> int GCPtr<T, size>::gclistSize()
{ 
	return GCInfo::gclist.size();
}

#endif // end inclusion guard